import operator

from utils.prettyrepr import prettify_class
from utils.misc import INF


@prettify_class
class OptimizationSense(object):
    """An optimization sense is used to indicate whether we're minimizing or maximizing some
    function. It provides a 'is_better(a, b)' boolean function that returns True if 'a' is better
    than 'b' in the given sense, and False otherwise. The functions 'operator.lt' (<) and
    'operator.gt' (>) are used for minimization and maximization, respectively.
    Likewise, the sense also defines what is the best value in a list of values through its
    'best_in(iterable, key=None)' function (this is min() and max() for the two default senses).
    """
    __slots__ = ("name", "is_better", "best_in", "best_value", "worst_value")

    def __init__(self, name, is_better, best_in, best_value, worst_value):
        self.name = name
        self.is_better = is_better
        self.best_in = best_in
        self.best_value = best_value
        self.worst_value = worst_value

    def __info__(self):
        return self.name


MIN = OptimizationSense(name="min",
                        is_better=operator.lt,
                        best_in=min,
                        best_value=-INF,
                        worst_value=+INF)
MAX = OptimizationSense(name="max",
                        is_better=operator.gt,
                        best_in=max,
                        best_value=+INF,
                        worst_value=-INF)
SENSE_MAP = {}


def register_sense(sense, *synonyms):
    SENSE_MAP.update((synonym, sense) for synonym in synonyms)


def get_sense(sense):
    if not isinstance(sense, OptimizationSense):
        if isinstance(sense, basestring):
            sense = sense.strip().lower()
        try:
            sense = SENSE_MAP[sense]
        except KeyError:
            raise ValueError("unexpected sense: {!r}".format(sense))
    return sense


register_sense(MIN, min, "min", "minimize", "minimization", "<", operator.lt, -1)
register_sense(MAX, max, "max", "maximize", "maximization", ">", operator.gt, +1)


@prettify_class
class ObjectiveFunction(object):
    """An objective function contains two parts: a function taking a solution as single argument
    and returning its value; and a sense, which defines how objective values compare to each other,
    i.e. whether we're maximizing or minimizing.
    """
    __slots__ = ("sense", "function")

    def __init__(self, sense, function):
        self.sense = get_sense(sense)
        self.function = function

    def __info__(self):
        return "{} {}".format(self.sense.name, self.function.__name__)
